#include #include #include Adafruit_SSD1306 display(128, 64, &Wire, 4); RTC_DS3231 rtc; int hours, minutes, seconds; #define YELLOW_ZONE 16 void TCA9548A(uint8_t bus) { Wire.beginTransmission(0x70); Wire.write(1 << bus); Wire.endTransmission(); } void setup() { // Initialize RTC on channel 1 TCA9548A(1); rtc.begin(); if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } // --- OPTIONAL MANUAL TIME SET (only enable when needed) --- // To set time manually, remove the comment marks from the lines below, // adjust the DateTime parameters (Year, Month, Day, Hour, Minute, Second), // upload once, then comment them out again to prevent overwriting the RTC each boot. // rtc.adjust(DateTime(2025, 10, 22, 12, 15, 00)); // YYYY, MM, DD, HH, MM, SS // Initialize displays with rotated orientation // Display 1: Seconds (leftmost) - Channel 2 TCA9548A(2); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setRotation(1); // Rotate 90 degrees for portrait mode display.clearDisplay(); display.display(); // Display 2: Minutes (middle) - Channel 3 TCA9548A(3); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setRotation(1); // Rotate 90 degrees for portrait mode display.clearDisplay(); display.display(); // Display 3: Hours (rightmost) - Channel 4 TCA9548A(4); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setRotation(1); // Rotate 90 degrees for portrait mode display.clearDisplay(); display.display(); delay(500); showIntro(); // delay(1000); } void showIntro() { int screenH = 128; const char* words[3] = {"CLOCK", "DISPL", "THREE"}; // --- STEP 1: Loading animation + falling letters --- for (int h = 0; h <= 120; h += 6) { for (int idx = 0; idx < 3; idx++) { int ch = 2 + idx; TCA9548A(ch); display.clearDisplay(); // --- Yellow frame (16x128, 2-px border) --- for (int i = 0; i < 2; i++) { display.drawRect(i, i, YELLOW_ZONE - 2 * i, screenH - 2 * i, WHITE); } // --- Filling bar --- int fillX = 4; int fillW = YELLOW_ZONE - 8; int fillY = 124 - h; display.fillRect(fillX, fillY, fillW, h, WHITE); // --- Falling letters --- display.setTextColor(WHITE); display.setTextSize(2); int textX = YELLOW_ZONE + 20; int textY = 12; const char* w = words[idx]; // Determine how many letters to show depending on progress int totalLetters = strlen(w); int visibleLetters = map(h, 0, 120, 0, totalLetters); if (visibleLetters > totalLetters) visibleLetters = totalLetters; // Draw visible letters vertically, one by one for (int k = 0; k < visibleLetters; k++) { display.setCursor(textX, textY + k * 20); display.write(w[k]); } display.display(); } delay(80); } delay(1000); // pause 1 s at full bar // --- STEP 2: Fade-out animation (bars emptying downward) --- for (int h = 120; h >= 0; h -= 6) { for (int idx = 0; idx < 3; idx++) { int ch = 2 + idx; TCA9548A(ch); display.clearDisplay(); // --- Yellow frame --- for (int i = 0; i < 2; i++) { display.drawRect(i, i, YELLOW_ZONE - 2 * i, screenH - 2 * i, WHITE); } // --- Emptying bar --- int fillX = 4; int fillW = YELLOW_ZONE - 8; int fillY = 124 - h; display.fillRect(fillX, fillY, fillW, h, WHITE); // --- Keep all letters visible during fade --- display.setTextColor(WHITE); display.setTextSize(2); int textX = YELLOW_ZONE + 20; int textY = 12; const char* w = words[idx]; for (int k = 0; w[k]; k++) { display.setCursor(textX, textY + k * 20); display.write(w[k]); } display.display(); } delay(60); } // --- STEP 3: Clear all displays --- for (int ch = 2; ch <= 4; ch++) { TCA9548A(ch); display.clearDisplay(); display.display(); } } void loop() { // Read time from RTC TCA9548A(1); DateTime now = rtc.now(); hours = now.hour(); minutes = now.minute(); seconds = now.second(); // Update Seconds Display (Left) - Channel 2 TCA9548A(2); display.clearDisplay(); drawDisplay(seconds, 'S', 60, seconds); display.display(); // Update Minute Display (Middle) - Channel 3 TCA9548A(3); display.clearDisplay(); drawDisplay(minutes, 'M', 60, minutes); display.display(); // Update Hours Display (Right) - Channel 4 TCA9548A(4); display.clearDisplay(); drawDisplay(hours, 'H', 24, hours); display.display(); delay(200); // Update 5 times per second } void drawDisplay(int value, char unit, int maxValue, int barValue) { // NOTE: In portrait rotation, display.width() = 64, display.height() = 128 int screenW = 64; int screenH = 128; // --- Bargraph container --- int barX = 0; int barW = YELLOW_ZONE; // 16 px int barY = 0; int barH = screenH; // Outer rectangle (frame, 2-px thick) for (int i = 0; i < 2; i++) { display.drawRect(barX + i, barY + i, barW - 2 * i, barH - 2 * i, WHITE); } // --- Calculate bar height (usable area = 120 px: from y=4 to y=124) --- int innerTop = 4; int innerBottom = screenH - 4; int usableHeight = innerBottom - innerTop; // 120 px int barHeight = map(barValue, 0, maxValue, 0, usableHeight); // --- Fill the bar inside the container --- int fillX = barX + 2 + 2; // 2-px frame + 2-px gap int fillW = barW - 8; // 2+2 margin each side → 8 total int fillY = innerBottom - barHeight; // fill upward from bottom display.fillRect(fillX, fillY, fillW, barHeight, WHITE); // --- Blue field dimensions (right side) --- int frameX1 = YELLOW_ZONE; int frameW = screenW - YELLOW_ZONE; int frameY1 = 0; int frameH = screenH; // --- Outer frame (3 px) --- for (int i = 0; i < 3; i++) { display.drawRect(frameX1 + i, frameY1 + i, frameW - 2 * i, frameH - 2 * i, WHITE); } // --- Divider between letter and digits --- int dividerY = 40; display.fillRect(frameX1 + 3, dividerY, frameW - 6, 3, WHITE); // --- Text --- display.setTextColor(WHITE); display.setTextSize(3); // Upper: unit letter (H / M / S) int unitX = frameX1 + 17; int unitY = 11; display.setCursor(unitX, unitY); display.print(unit); display.setTextSize(4); // Lower: digits int tens = value / 10; int units = value % 10; int tensX = frameX1 + 13; int tensY = 52; display.setCursor(tensX, tensY); display.print(tens); int unitsX = frameX1 + 13; int unitsY = 87; display.setCursor(unitsX, unitsY); display.print(units); // --- Inner rounded frames --- // Top rectangle (letters) int innerTopX = frameX1 + 4; int innerTopY = 4; int innerTopW = frameW - 8; int innerTopH = dividerY - innerTopY - 2; // 1px gap to divider for (int i = 0; i < 2; i++) { display.drawRoundRect(innerTopX + i, innerTopY + i, innerTopW - 2 * i, innerTopH - 2 * i, 4, WHITE); } // Bottom rectangle (digits) int innerBotX = frameX1 + 4; int innerBotY = dividerY + 4; // 1px gap below divider int innerBotW = frameW - 8; int innerBotH = frameH - innerBotY - 4; for (int i = 0; i < 2; i++) { display.drawRoundRect(innerBotX + i, innerBotY + i, innerBotW - 2 * i, innerBotH - 2 * i, 4, WHITE); } }